import 'package:flutter/cupertino.dart';

import 'package:sqflite_demo/page/home_page.dart';
import 'package:sqflite_demo/page/shelf_page.dart';
import 'package:sqflite_demo/page/show_data.dart';
import 'package:sqflite_demo/reading/Read.dart';

import 'RouterHelper.dart';
import 'package:flutter/material.dart';


const Type HOME = HomePage;
final _routes = Helper.routeDefine({
  Read : (_) => Read(),
  ShelfPage : (_) => ShelfPage(),
  ShowData : (_) => ShowData(),
});


class MyRouter {

  // 设置成私有构造，
  MyRouter._();

  // 临时变量用于保存路由参数
  // -------------------------
  Object? _arguments;
  String? _routeName;
  BuildContext? _context;
  // -------------------------

  /// 路由定义
  static final ROUTES = _routes;

  /// 单实例对象
  static final MyRouter _instance = MyRouter._();

  /// 对外部公开首页的路由名
  static final String INDEX = Helper.typeName(HOME);

  /// 重置临时变量
  void _resetVariables() {
    _arguments = null;
    _routeName = null;
    _context = null;
  }

  /// 携带参数跳转
  Future<T?> to<T extends Object?>() {
    assert(_context != null);
    assert(_routeName != null);
    return Navigator.pushNamed<T>(
      _context!,
      _routeName!,
      arguments: _arguments,
      // 这里当整个流程终结时需要重置临时变量，以节省内存空间。
    ).whenComplete(_resetVariables);
  }

  /// 静态设置参数，这个方法主要是用于在路由跳转时调用
  /// 然后通过上面设置的临时变量进行保存，最后调用 to 时传递过去
  static MyRouter _withArguments(dynamic arguments) {
    _instance._arguments = arguments;
    return _instance;
  }

  /// 这里就是确定路由参数的核心
  /// 由于定义路由时是通过：(_) => Page() 这种方式；
  /// 所以这里可以利用这种规则，我们先对这个函数调用一次，这样的话可以获取到对应页面的实例对象；
  /// 因为页面在混入 RouterBridge 类时就已经确定好了参数类型，所以我们这里我们直接返回
  /// 具体的页面实例对象，就可以调用 RouterBridge 混入类中的 arguments 方法，
  /// 而当我们调用 arguments 方法时，内部又是返回的 MyRouter 单例对象，所以就将外部设置的
  /// 参数又传回来了，然后保存到上面声明的临时变量中。
  static RT of<RT extends RouterBridge>(BuildContext context) {
    assert(RT != RouterBridge<dynamic>,
    "You must specify the route type, for example: of<Page>(context)");
    final name = Helper.typeName(RT);
    assert(ROUTES.containsKey(name), "Route \"$RT\" is not registered.");
    _instance._context = context; // 临时保存 context
    _instance._routeName = name; // 临时保存路由名
    var builder = ROUTES[name]!; // 取得 WidgetBuilder 对象
    // 这里调用一次就可以获得对应的页面实例对象
    // 既然获得了实例对象，上面的页面又混入了对应的 RouterBridge，在后续调用 arguments 方法时
    // 就能唯一的确定具体路由需要的参数类型
    return builder.call(context) as RT;
  }

  /// 不带任何参数的路由跳转
  static Future<T?> routeTo<T extends Object?>(BuildContext context, Type router) {
    final name = Helper.typeName(router); // 将传入的 Type 转换成字符串
    assert(_routes.containsKey(name), "Route \"$router\" is not registered.");
    return Navigator.pushNamed<T>(context, name);
  }

  // 这个是在 MaterialApp 定义路由中需要使用的
  static PageRouteBuilder<T> onGenerateRoute<T>(RouteSettings settings) {
    final builder = ROUTES[settings.name] ?? ROUTES[INDEX]!;

    return PageRouteBuilder<T>(
      settings: settings,
      pageBuilder: (context, animation, secondaryAnimation) => builder(context),
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        // 自定义过渡动画
        return SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(1.0 , 0.0), // 起始位置（微调避免突兀）
            end: Offset.zero,
          ).animate(
            CurvedAnimation(
              parent: animation,
              curve: Curves.fastOutSlowIn, // 动画曲线
            ),
          ),
          child: FadeTransition(
            opacity: AlwaysStoppedAnimation(1),
            child: child,
          ),
        );
      },
      transitionDuration: const Duration(milliseconds: 250), // 动画时长
    );
  }
}


// 混入类一定要放在和 MyRouter 同一级，这样才可以隐藏 MyRouter._withArguments 的实现
// 避免对外部暴露不需要的方法
mixin RouterBridge<RT_ARG_TYPE> {
  /// 用于设置理由参数
  MyRouter arguments(RT_ARG_TYPE args) => MyRouter._withArguments(args);

  /// 用于还原路由参数到具体的数据类型上
  /// 这样在页面中就可以调用这个方法获取具体的参数类型了
  RT_ARG_TYPE? argumentOf(BuildContext context) {
    final Object? arguments = ModalRoute.of(context)?.settings.arguments;
    return arguments == null ? null : arguments as RT_ARG_TYPE;
  }
}